package com.qying.myguava.hash;

import org.checkerframework.checker.nullness.qual.Nullable;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.Random;

/**
 * @Author 秦扬
 * @Date 2021/9/15 16:04
 */
abstract class Striped64 extends Number{

    static final class Cell {
        volatile long p0,p1,p2,p3,p4,p5,p6;
        volatile long value;
        volatile long q0,q1,q2,q3,q4,q5,q6;

        Cell(long x) {
            value = x;
        }

        private static final sun.misc.Unsafe UNSAFE;

        private static final long valueOffset;

        final boolean cas (long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset,cmp,val);
        }

        static {
            try {
                UNSAFE = getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset(ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }

    }

    static final ThreadLocal<int @Nullable []>  threadHashCode = new ThreadLocal<>();

    static final Random rng = new Random();

    static final int NCPU = Runtime.getRuntime().availableProcessors();

    transient volatile Cell[] cells;

    transient volatile long base;

    transient volatile long busy;

    Striped64(){}

    final boolean casBase(long cmp, long val) {
        return Unsafe.getUnsafe().compareAndSwapLong(this, baseOffset, cmp, val);
    }

    final boolean casBusy(){
        return UNSAFE.compareAndSwapInt(this,busyOffset,0,1);
    }

    abstract long fn(long currentValue, long newValue);

    final void retryUpdate(long x, int[] hc, boolean wasUncontened) {
        int h;
        if (hc == null) {
            threadHashCode.set(hc = new int[1]);

            int r = rng.nextInt();
            h = hc[0] = (r == 0) ? 1: r;
        }else {
            h = hc[0];
        }
        boolean collide = false;

        for (;;) {
            Cell[] as;
            Cell a;
            int n;
            long v;
            if ((as = cells) != null && (n=as.length) > 0) {
                if ((a= as[(n-1) & h]) == null) {
                    if (busy == 0) {
                        Cell r = new Cell(x);

                        if (busy == 0 && casBusy()) {
                            boolean created = false;
                            try {
                                Cell[] rs;
                                int m,j;
                                if ((rs = cells) != null && (m = rs.length) > 0 && rs[j=(m-1) & h] == null) {
                                    rs[j] = r;

                                    created = true;
                                }
                            }finally {
                                busy = 0;
                            }
                            if (created) break;
                            continue;
                        }
                    }
                    collide = false;
                }else if (!wasUncontened){
                    wasUncontened = true;
                }else if (a.cas(v = a.value, fn(v,x))){
                    break;
                }else if (n >=NCPU || cells !=as){
                    collide = false;
                }else if (!collide) {
                    collide = true;
                }else if (busy == 0 && casBusy()) {
                    try {
                        if (cells == as) {
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0 ; i< n;++i) {
                                rs[i] =as[i];
                                cells = rs;
                            }
                        }
                    }finally {
                        busy = 0;
                    }
                    collide = false;
                    continue;
                }
                h ^= h<<13;
                h ^= h>>>17;
                h ^= h<<5;
                hc[0] = h;

            }else if (busy ==0&& cells==as && casBusy()) {
                boolean init = false;
                try {
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                }finally {
                    busy = 0;
                }
                if (init) break;

            }else if (casBase(v= base, fn(v,x))) break;
        }
    }

    final void internalReset(long initValue) {
        Cell[] as = cells;
        base = initValue;
        if (as != null) {
            int n= as.length;
            for (int i =0; i<n;++i) {
                Cell a = as[i];
                if (a != null) {
                    a.value = initValue;
                }
            }
        }
    }
    private static final sun.misc.Unsafe UNSAFE;
    private static final long baseOffset;
    private static final long busyOffset;

    static {
        try {
            UNSAFE = Unsafe.getUnsafe();
            Class<Striped64> sk = Striped64.class;
            baseOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("base"));
            busyOffset = UNSAFE.objectFieldOffset(sk.getDeclaredField("busy"));
        }catch (Exception e) {
            throw new Error(e);
        }
    }

    private static sun.misc.Unsafe getUnsafe(){
        try {
            return sun.misc.Unsafe.getUnsafe();
        }catch (SecurityException securityException) {}

        try {
            return java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>(){

                        @Override
                        public Unsafe run() throws Exception {
                            Class<Unsafe> k = Unsafe.class;
                            for (Field f : k.getDeclaredFields()) {
                                f.setAccessible(true);
                                Object o = f.get(null);
                                if (k.isInstance(o)) {
                                    return k.cast(o);
                                }
                            }
                            throw new NoSuchFieldException("the Unsafe");
                        }
            });
        }catch (java.security.PrivilegedActionException e) {
            throw new RuntimeException("Could not initialize intrinsics", e.getCause());
        }
    }



}
